#include "userprog/syscall.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
#include "devices/shutdown.h"
#include "threads/vaddr.h"
#include "filesys/filesys.h"
#include <string.h>
#include <filesys/filesys.h>
#include "threads/synch.h"
#include <threads/malloc.h>
#include "pagedir.h"
#include <list.h>
#include <filesys/file.h>
#include "process.h"

static int get_user(const uint8_t *);
static bool put_user(uint8_t *, uint8_t);
static void syscall_handler(struct intr_frame *);

void syscall_halt(void);
void syscall_exit(struct intr_frame *);
void syscall_exec(struct intr_frame *);
void syscall_wait(struct intr_frame *);
void syscall_create(struct intr_frame *);
void syscall_remove(struct intr_frame *);
void syscall_open(struct intr_frame *);
void syscall_filesize(struct intr_frame *);
void syscall_read(struct intr_frame *);
void syscall_write(struct intr_frame *);
void syscall_seek(struct intr_frame *);
void syscall_tell(struct intr_frame *);
void syscall_close(struct intr_frame *);

static bool is_valid_buffer(char *, int);
static bool is_valid_file(char *);
static int *get_argument(void *, int);
// 依据fd查找file
static struct opened_file *get_file(int);

void exit(int);

/* Reads a byte at user virtual address UADDR.
   UADDR must be below PHYS_BASE.
   Returns the byte value if successful, -1 if a segfault
   occurred. */
static int
get_user(const uint8_t *uaddr)
{
  if (!is_user_vaddr((void *)uaddr))
    return -1;

  int result;
  asm("movl $1f, %0; movzbl %1, %0; 1:"
      : "=&a"(result)
      : "m"(*uaddr));
  return result;
}

/* Writes BYTE to user address UDST.
   UDST must be below PHYS_BASE.
   Returns true if successful, false if a segfault occurred. */
static bool
put_user(uint8_t *udst, uint8_t byte)
{
  int error_code;
  asm("movl $1f, %0; movb %b2, %1; 1:"
      : "=&a"(error_code), "=m"(*udst)
      : "q"(byte));
  return error_code != -1;
}

void syscall_init(void)
{
  intr_register_int(0x30, 3, INTR_ON, syscall_handler, "syscall");
}

static void
syscall_handler(struct intr_frame *f UNUSED)
{
  int syscall_number = *(uint8_t *)get_argument(f->esp, 0);

  // 选择系统调用类型
  switch (syscall_number)
  {
  case SYS_HALT:
    syscall_halt();
    break;
  case SYS_EXIT:
    syscall_exit(f);
    break;
  case SYS_EXEC:
    syscall_exec(f);
    break;
  case SYS_WAIT:
    syscall_wait(f);
    break;
  case SYS_CREATE:
    syscall_create(f);
    break;
  case SYS_REMOVE:
    syscall_remove(f);
    break;
  case SYS_OPEN:
    syscall_open(f);
    break;
  case SYS_FILESIZE:
    syscall_filesize(f);
    break;
  case SYS_READ:
    syscall_read(f);
    break;
  case SYS_WRITE:
    syscall_write(f);
    break;
  case SYS_SEEK:
    syscall_seek(f);
    break;
  case SYS_TELL:
    syscall_tell(f);
    break;
  case SYS_CLOSE:
    syscall_close(f);
    break;
  default:
    exit(-1);
    break;
  }
}

void syscall_halt(void)
{
  shutdown_power_off();
}

void syscall_exit(struct intr_frame *f)
{
  int exit_code = *(int *)get_argument(f->esp, 1);
  f->eax = exit_code;
  exit(exit_code);
}

void syscall_exec(struct intr_frame *f)
{
  char *cmd_line = *(char **)get_argument(f->esp, 1);

  if (cmd_line == NULL || !is_valid_file(cmd_line))
  {
    exit(-1);
  }

  f->eax = process_execute(cmd_line);
}

void syscall_wait(struct intr_frame *f)
{
  int pid = *(int *)get_argument(f->esp, 1);

  f->eax = process_wait(pid);
}

void syscall_create(struct intr_frame *f)
{
  char *file = *(char **)get_argument(f->esp, 1);
  unsigned initial_size = *(unsigned *)get_argument(f->esp, 2);

  if (file == NULL || !is_valid_file(file))
  {
    exit(-1);
  }

  acquire_file_lock();
  f->eax = filesys_create(file, initial_size);
  release_file_lock();
}

void syscall_remove(struct intr_frame *f)
{
  char *file = *(char **)get_argument(f->esp, 1);

  if (file == NULL || !is_valid_file(file))
  {
    exit(-1);
  }

  acquire_file_lock();
  f->eax = filesys_remove(file);
  release_file_lock();
}

void syscall_open(struct intr_frame *f)
{
  char *file = *(char **)get_argument(f->esp, 1);

  if (file == NULL || !is_valid_file(file))
  {
    exit(-1);
  }

  acquire_file_lock();
  struct file *file_opened = filesys_open(file);
  release_file_lock();
  struct thread *t = thread_current();

  if (file_opened)
  {
    struct opened_file *temp = malloc(sizeof(struct opened_file));
    if (t->freefd < 3)
    {
      t->freefd = 3;
    }
    else
    {
      t->freefd++;
    }
    temp->fd = t->freefd;
    temp->file = file_opened;
    list_push_back(&t->files, &temp->elem);
    f->eax = temp->fd;
  }
  else
  {
    f->eax = -1;
  }
}

void syscall_filesize(struct intr_frame *f)
{
  int fd = *(int *)get_argument(f->esp, 1);
  struct opened_file *temp = get_file(fd);

  if (temp == NULL)
  {
    exit(-1);
  }

  acquire_file_lock();
  f->eax = file_length(temp->file);
  release_file_lock();
}

void syscall_read(struct intr_frame *f)
{
  int fd = *(int *)get_argument(f->esp, 1);
  void *buffer = *(char **)get_argument(f->esp, 2);
  unsigned size = *(unsigned *)get_argument(f->esp, 3);

  if (buffer == NULL || !is_valid_buffer(buffer, size))
  {
    exit(-1);
  }

  // 标准输入
  if (fd == STDIN_FILENO)
  {
    for (int i = 0; i < size; i++)
    {
      *((char *)buffer + i) = input_getc();
    }
    f->eax = size;
  }
  // 文件输入
  else
  {
    struct opened_file *temp = get_file(fd);
    if (temp != NULL)
    {
      acquire_file_lock();
      f->eax = file_read(temp->file, buffer, size);
      release_file_lock();
    }
    else
    {
      exit(-1);
    }
  }
}

void syscall_write(struct intr_frame *f)
{
  int fd = *(int *)get_argument(f->esp, 1);
  void *buffer = *(char **)get_argument(f->esp, 2);
  unsigned size = *(unsigned *)get_argument(f->esp, 3);

  if (buffer == NULL || !is_valid_buffer(buffer, size))
  {
    exit(-1);
  }

  // 标准输出
  if (fd == STDOUT_FILENO)
  {
    putbuf(buffer, size);
    f->eax = size;
  }
  // 文件输出
  else
  {
    struct opened_file *temp = get_file(fd);
    if (temp != NULL)
    {
      acquire_file_lock();
      f->eax = file_write(temp->file, buffer, size);
      release_file_lock();
    }
    else
    {
      exit(-1);
    }
  }
}

void syscall_seek(struct intr_frame *f)
{
  int fd = *(int *)get_argument(f->esp, 1);
  int pos = *(int *)get_argument(f->esp, 2);
  struct opened_file *temp_file = get_file(fd);
  if (temp_file != NULL)
  {
    acquire_file_lock();
    file_seek(temp_file->file, pos);
    release_file_lock();
  }
  else
  {
    exit(-1);
  }
}

void syscall_tell(struct intr_frame *f)
{
  int fd = *(int *)get_argument(f->esp, 1);
  struct opened_file *temp_file = get_file(fd);
  if (temp_file != NULL)
  {
    acquire_file_lock();
    f->eax = file_tell(temp_file->file);
    release_file_lock();
  }
  else
  {
    exit(-1);
  }
}

void syscall_close(struct intr_frame *f)
{
  int fd = *(int *)get_argument(f->esp, 1);
  struct thread *t = thread_current();
  struct opened_file *temp_file = get_file(fd);
  if (temp_file)
  {
    acquire_file_lock();
    file_close(temp_file->file);
    release_file_lock();
    list_remove(&temp_file->elem);
    free(temp_file);
  }
}

void exit(int exit_code)
{
  thread_current()->exit_code = exit_code;
  thread_exit();
}

static bool is_valid_buffer(char *buffer, int size)
{
  return get_user((uint8_t *)buffer) != -1 && get_user((uint8_t *)buffer + size - 1) != -1;
}

static bool is_valid_file(char *file)
{
  for (uint8_t i = 0; i < 4; i++)
  {
    if (get_user((uint8_t *)file + i) == -1)
    {
      exit(-1);
    }
  }

  return true;
}

static int *get_argument(void *esp, int offset)
{
  int *arg = (int *)esp + offset;

  for (uint8_t i = 0; i < 4; i++)
  {
    if (get_user((uint8_t *)arg + i) == -1)
    {
      exit(-1);
    }
  }

  return arg;
}

static struct opened_file *get_file(int fd)
{
  struct list_elem *e;
  struct opened_file *temp = NULL;
  struct list *files = &thread_current()->files;
  for (e = list_begin(files); e != list_end(files); e = list_next(e))
  {
    temp = list_entry(e, struct opened_file, elem);
    if (fd == temp->fd)
      return temp;
  }
  return NULL;
}